package com.wmada.toolkit.rest;

import com.wmada.toolkit.rest.builder.EntityBuilder;
import com.wmada.toolkit.rest.builder.HeaderBuilder;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;

import java.io.File;
import java.util.*;

/**
 * @author will.ma
 * @date 2020-3-2
 */
@SuppressWarnings("unused")
public class Downloader {

    static final DownloadProxy INSTANCE = new DownloadProxy();

    public enum EntityMode {
        /**
         *
         */
        WILDCARD(ContentType.WILDCARD),
        TEXT_HTML(ContentType.TEXT_HTML),
        TEXT_PLAIN(ContentType.TEXT_PLAIN),
        FORM_URLENCODED(ContentType.APPLICATION_FORM_URLENCODED),
        JSON(ContentType.APPLICATION_JSON),
        MULTIPART_FROM_DATA(ContentType.MULTIPART_FORM_DATA),
        STREAM(ContentType.APPLICATION_OCTET_STREAM),
        XML(ContentType.TEXT_XML, ContentType.APPLICATION_XML),
        IMAGE_BMP(ContentType.IMAGE_BMP),
        IMAGE_GIF(ContentType.IMAGE_GIF),
        IMAGE_JPEG(ContentType.IMAGE_JPEG),
        IMAGE_PNG(ContentType.IMAGE_PNG),
        IMAGE_SVG(ContentType.IMAGE_SVG),
        IMAGE_TIFF(ContentType.IMAGE_TIFF),
        IMAGE_WEBP(ContentType.IMAGE_WEBP);

        ContentType[] contentTypes;

        EntityMode(ContentType... contentType) {
            this.contentTypes = contentType;
        }

        public ContentType[] getContentTypes() {
            return contentTypes;
        }
    }

    private static final String AJAX_KEY   = "X-Requested-With";
    private static final String AJAX_VALUE = "XMLHttpRequest";

    private String  userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36";
    private String  referer   = "";
    private boolean isAjax    = false;

    private EntityMode mode = EntityMode.TEXT_HTML;

    private final List<Header>                  headers = new ArrayList<>();
    private final LinkedHashMap<String, String> cookie  = new LinkedHashMap<>();
    private final LinkedHashMap<String, String> params  = new LinkedHashMap<>();
    private final LinkedHashMap<String, File>   files   = new LinkedHashMap<>();

    private Object   data         = null;
    private Response lastResponse = null;

    public Downloader reset() {
        userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36";
        referer = "";
        isAjax = false;

        mode = EntityMode.TEXT_HTML;

        cookie.clear();
        params.clear();
        files.clear();
        data = null;
        lastResponse = null;

        return this;
    }

    public String getUserAgent() {
        return userAgent;
    }

    public void setUserAgent(String userAgent) {
        this.userAgent = userAgent;
    }

    public String getReferer() {
        return referer;
    }

    public void setReferer(String referer) {
        this.referer = referer;
    }

    public boolean isAjax() {
        return isAjax;
    }

    public void setIsAjax(boolean ajax) {
        isAjax = ajax;
    }

    public EntityMode getMode() {
        return mode;
    }

    public void setMode(EntityMode mode) {
        this.mode = mode;
    }

    public List<Header> getHeaders() {
        return headers;
    }

    public void setHeaders(List<Header> headers) {
        this.headers.addAll(headers);
    }

    public LinkedHashMap<String, String> getCookie() {
        return cookie;
    }

    public void setCookie(LinkedHashMap<String, String> cookie) {
        this.cookie.putAll(cookie);
    }

    public LinkedHashMap<String, String> getParams() {
        return params;
    }

    public void setParams(LinkedHashMap<String, String> data) {
        this.params.putAll(data);
    }

    public LinkedHashMap<String, File> getFiles() {
        return files;
    }

    public void setFiles(LinkedHashMap<String, File> files) {
        this.files.putAll(files);
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public Response getLastResponse() {
        return lastResponse;
    }


    public Downloader withUserAgent(String userAgent) {
        this.userAgent = userAgent;
        return this;
    }

    public Downloader withReferer(String referer) {
        this.referer = referer;
        return this;
    }

    public Downloader withAjax() {
        isAjax = true;
        return this;
    }

    public Downloader withMode(EntityMode mode) {
        this.mode = mode;
        return this;
    }

    public Downloader useJson() {
        this.mode = EntityMode.JSON;
        return this;
    }

    public Downloader useForm() {
        this.mode = EntityMode.MULTIPART_FROM_DATA;
        return this;
    }

    public Downloader useFormUrlEncoded() {
        this.mode = EntityMode.FORM_URLENCODED;
        return this;
    }

    public Downloader withHeader(Header... headers) {
        this.headers.addAll(Arrays.asList(headers));
        return this;
    }

    public Downloader withLastResponseCookie() {
        if (lastResponse != null) {
            setCookie(lastResponse.getCookies());
        }
        return this;
    }

    public Downloader withCookie(String... kvs) {
        if (kvs.length % 2 != 0) {
            throw new IllegalArgumentException("With Cookie Only accepts kvs.");
        }
        for (int i = 0; i < kvs.length / 2; i++) {
            cookie.put(kvs[2 * i], kvs[2 * i + 1]);
        }
        return this;
    }

    public Downloader withParam(String... kvs) {
        if (kvs.length % 2 != 0) {
            throw new IllegalArgumentException("With Param Only accepts kvs.");
        }
        for (int i = 0; i < kvs.length / 2; i++) {
            params.put(kvs[2 * i], kvs[2 * i + 1]);
        }
        return this;
    }

    public Downloader withParam(Map<String, Object> params) {
        params.forEach((k, v) -> this.params.put(k, v != null ? v.toString() : ""));
        return this;
    }

    public Downloader withFile(String name, File file) {
        this.files.put(name, file);
        return this;
    }

    public Downloader withFile(File file) {
        this.files.put(file.getName(), file);
        return this;
    }

    public Downloader withData(Object data) {
        this.data = data;
        return this;
    }

    private Header[] getHeader() {
        HeaderBuilder builder = HeaderBuilder.create();
        if (StringUtils.isNotBlank(userAgent)) {
            builder.userAgent(userAgent);
        }
        if (StringUtils.isNotBlank(referer)) {
            builder.referer(referer);
        }
        if (!cookie.isEmpty()) {
            builder.cookie(cookie);
        }
        if (isAjax) {
            builder.add(AJAX_KEY, AJAX_VALUE);
        }
        builder.accept(mode.getContentTypes());
        return builder.build();
    }

    public Map<String, String[]> getUrlParams() {
        Map<String, String[]> params = new LinkedHashMap<>(this.params.size());
        this.params.forEach((k, v) -> params.put(k, new String[]{v}));
        return params;
    }

    public HttpEntity getEntity() {
        switch (mode) {
            case FORM_URLENCODED:
                return EntityBuilder.buildForm(params);
            case MULTIPART_FROM_DATA:
                return EntityBuilder.buildMultipart(params, ContentType.MULTIPART_FORM_DATA.getCharset(), files);
            case JSON:
                return EntityBuilder.buildJson(data);
            default:
                // Do nothing.
        }
        return EntityBuilder.buildForm(params);
    }

    public Response head(String url) {
        lastResponse = INSTANCE.head(url, getUrlParams(), getHeader());
        return lastResponse;
    }

    public Response options(String url) {
        lastResponse = INSTANCE.options(url, getUrlParams(), getHeader());
        return lastResponse;
    }

    public Response get(String url) {
        lastResponse = INSTANCE.get(url, getUrlParams(), getHeader());
        return lastResponse;
    }

    public Response put(String url) {
        if (mode == EntityMode.JSON) {
            lastResponse = INSTANCE.put(url, getUrlParams(), getHeader(), getEntity());
        } else {
            lastResponse = INSTANCE.put(url, null, getHeader(), getEntity());
        }
        return lastResponse;
    }

    public Response post(String url) {
        if (mode == EntityMode.JSON) {
            lastResponse = INSTANCE.post(url, getUrlParams(), getHeader(), getEntity());
        } else {
            lastResponse = INSTANCE.post(url, null, getHeader(), getEntity());
        }
        return lastResponse;
    }

    public Response patch(String url) {
        if (mode == EntityMode.JSON) {
            lastResponse = INSTANCE.patch(url, getUrlParams(), getHeader(), getEntity());
        } else {
            lastResponse = INSTANCE.patch(url, null, getHeader(), getEntity());
        }
        return lastResponse;
    }

    public Response delete(String url) {
        lastResponse = INSTANCE.delete(url, getUrlParams(), getHeader());
        return lastResponse;
    }

}
